home *** CD-ROM | disk | FTP | other *** search
/ EuroCD 3 / EuroCD 3.iso / Programming / Python-1.4 / Lib / formatter.py < prev    next >
Text File  |  1998-06-24  |  11KB  |  405 lines

  1. import regex
  2. import regsub
  3. import string
  4. import sys
  5. from types import StringType
  6.  
  7.  
  8. AS_IS = None
  9.  
  10.  
  11. class NullFormatter:
  12.  
  13.     def __init__(self, writer=None):
  14.     if not writer:
  15.         writer = NullWriter()
  16.     self.writer = writer
  17.     def end_paragraph(self, blankline): pass
  18.     def add_line_break(self): pass
  19.     def add_hor_rule(self, *args, **kw): pass
  20.     def add_label_data(self, format, counter, blankline=None): pass
  21.     def add_flowing_data(self, data): pass
  22.     def add_literal_data(self, data): pass
  23.     def flush_softspace(self): pass
  24.     def push_alignment(self, align): pass
  25.     def pop_alignment(self): pass
  26.     def push_font(self, x): pass
  27.     def pop_font(self): pass
  28.     def push_margin(self, margin): pass
  29.     def pop_margin(self): pass
  30.     def set_spacing(self, spacing): pass
  31.     def push_style(self, *styles): pass
  32.     def pop_style(self, n=1): pass
  33.     def assert_line_data(self, flag=1): pass
  34.  
  35.  
  36. class AbstractFormatter:
  37.  
  38.     #  Space handling policy:  blank spaces at the boundary between elements
  39.     #  are handled by the outermost context.  "Literal" data is not checked
  40.     #  to determine context, so spaces in literal data are handled directly
  41.     #  in all circumstances.
  42.  
  43.     def __init__(self, writer):
  44.     self.writer = writer        # Output device
  45.     self.align = None        # Current alignment
  46.     self.align_stack = []        # Alignment stack
  47.     self.font_stack = []        # Font state
  48.     self.margin_stack = []        # Margin state
  49.     self.spacing = None        # Vertical spacing state
  50.     self.style_stack = []        # Other state, e.g. color
  51.     self.nospace = 1        # Should leading space be suppressed
  52.     self.softspace = 0        # Should a space be inserted
  53.     self.para_end = 1        # Just ended a paragraph
  54.     self.parskip = 0        # Skipped space between paragraphs?
  55.     self.hard_break = 1        # Have a hard break
  56.     self.have_label = 0
  57.  
  58.     def end_paragraph(self, blankline):
  59.     if not self.hard_break:
  60.         self.writer.send_line_break()
  61.         self.have_label = 0
  62.     if self.parskip < blankline and not self.have_label:
  63.         self.writer.send_paragraph(blankline - self.parskip)
  64.         self.parskip = blankline
  65.         self.have_label = 0
  66.     self.hard_break = self.nospace = self.para_end = 1
  67.     self.softspace = 0
  68.  
  69.     def add_line_break(self):
  70.     if not (self.hard_break or self.para_end):
  71.         self.writer.send_line_break()
  72.         self.have_label = self.parskip = 0
  73.     self.hard_break = self.nospace = 1
  74.     self.softspace = 0
  75.  
  76.     def add_hor_rule(self, *args, **kw):
  77.     if not self.hard_break:
  78.         self.writer.send_line_break()
  79.     apply(self.writer.send_hor_rule, args, kw)
  80.     self.hard_break = self.nospace = 1
  81.     self.have_label = self.para_end = self.softspace = self.parskip = 0
  82.  
  83.     def add_label_data(self, format, counter, blankline = None):
  84.     if self.have_label or not self.hard_break:
  85.         self.writer.send_line_break()
  86.     if not self.para_end:
  87.         self.writer.send_paragraph((blankline and 1) or 0)
  88.     if type(format) is StringType:
  89.         self.writer.send_label_data(self.format_counter(format, counter))
  90.     else:
  91.         self.writer.send_label_data(format)
  92.     self.nospace = self.have_label = self.hard_break = self.para_end = 1
  93.     self.softspace = self.parskip = 0
  94.  
  95.     def format_counter(self, format, counter):
  96.         label = ''
  97.         for c in format:
  98.             try:
  99.                 if c == '1':
  100.             label = label + ('%d' % counter)
  101.                 elif c in 'aA':
  102.             if counter > 0:
  103.             label = label + self.format_letter(c, counter)
  104.                 elif c in 'iI':
  105.             if counter > 0:
  106.             label = label + self.format_roman(c, counter)
  107.         else:
  108.             label = label + c
  109.             except:
  110.                 label = label + c
  111.         return label
  112.  
  113.     def format_letter(self, case, counter):
  114.     label = ''
  115.     while counter > 0:
  116.         counter, x = divmod(counter-1, 26)
  117.         s = chr(ord(case) + x)
  118.         label = s + label
  119.     return label
  120.  
  121.     def format_roman(self, case, counter):
  122.         ones = ['i', 'x', 'c', 'm']
  123.         fives = ['v', 'l', 'd']
  124.         label, index = '', 0
  125.     # This will die of IndexError when counter is too big
  126.         while counter > 0:
  127.             counter, x = divmod(counter, 10)
  128.             if x == 9:
  129.                 label = ones[index] + ones[index+1] + label
  130.             elif x == 4:
  131.                 label = ones[index] + fives[index] + label
  132.             else:
  133.                 if x >= 5:
  134.                     s = fives[index]
  135.                     x = x-5
  136.                 else:
  137.                     s = ''
  138.                 s = s + ones[index]*x
  139.         label = s + label
  140.             index = index + 1
  141.         if case == 'I':
  142.         return string.upper(label)
  143.         return label
  144.  
  145.     def add_flowing_data(self, data,
  146.              # These are only here to load them into locals:
  147.              whitespace = string.whitespace,
  148.              join = string.join, split = string.split):
  149.     if not data: return
  150.     # The following looks a bit convoluted but is a great improvement over
  151.     # data = regsub.gsub('[' + string.whitespace + ']+', ' ', data)
  152.     prespace = data[:1] in whitespace
  153.     postspace = data[-1:] in whitespace
  154.     data = join(split(data))
  155.     if self.nospace and not data:
  156.         return
  157.     elif prespace or self.softspace:
  158.         if not data:
  159.         if not self.nospace:
  160.             self.softspace = 1
  161.             self.parskip = 0
  162.         return
  163.         if not self.nospace:
  164.         data = ' ' + data
  165.     self.hard_break = self.nospace = self.para_end = \
  166.               self.parskip = self.have_label = 0
  167.     self.softspace = postspace
  168.     self.writer.send_flowing_data(data)
  169.  
  170.     def add_literal_data(self, data):
  171.     if not data: return
  172.     if self.softspace:
  173.         self.writer.send_flowing_data(" ")
  174.     self.hard_break = data[-1:] == '\n'
  175.     self.nospace = self.para_end = self.softspace = \
  176.                self.parskip = self.have_label = 0
  177.     self.writer.send_literal_data(data)
  178.  
  179.     def flush_softspace(self):
  180.     if self.softspace:
  181.         self.hard_break = self.para_end = self.parskip = \
  182.                   self.have_label = self.softspace = 0
  183.         self.nospace = 1
  184.         self.writer.send_flowing_data(' ')
  185.  
  186.     def push_alignment(self, align):
  187.     if align and align != self.align:
  188.         self.writer.new_alignment(align)
  189.         self.align = align
  190.         self.align_stack.append(align)
  191.     else:
  192.         self.align_stack.append(self.align)
  193.  
  194.     def pop_alignment(self):
  195.     if self.align_stack:
  196.         del self.align_stack[-1]
  197.     if self.align_stack:
  198.         self.align = align = self.align_stack[-1]
  199.         self.writer.new_alignment(align)
  200.     else:
  201.         self.align = None
  202.         self.writer.new_alignment(None)
  203.  
  204.     def push_font(self, (size, i, b, tt)):
  205.     if self.softspace:
  206.         self.hard_break = self.para_end = self.softspace = 0
  207.         self.nospace = 1
  208.         self.writer.send_flowing_data(' ')
  209.     if self.font_stack:
  210.         csize, ci, cb, ctt = self.font_stack[-1]
  211.         if size is AS_IS: size = csize
  212.         if i is AS_IS: i = ci
  213.         if b is AS_IS: b = cb
  214.         if tt is AS_IS: tt = ctt
  215.     font = (size, i, b, tt)
  216.     self.font_stack.append(font)
  217.     self.writer.new_font(font)
  218.  
  219.     def pop_font(self):
  220.     if self.font_stack:
  221.         del self.font_stack[-1]
  222.     if self.font_stack:
  223.         font = self.font_stack[-1]
  224.     else:
  225.         font = None
  226.     self.writer.new_font(font)
  227.  
  228.     def push_margin(self, margin):
  229.     self.margin_stack.append(margin)
  230.     fstack = filter(None, self.margin_stack)
  231.     if not margin and fstack:
  232.         margin = fstack[-1]
  233.     self.writer.new_margin(margin, len(fstack))
  234.  
  235.     def pop_margin(self):
  236.     if self.margin_stack:
  237.         del self.margin_stack[-1]
  238.     fstack = filter(None, self.margin_stack)
  239.     if fstack:
  240.         margin = fstack[-1]
  241.     else:
  242.         margin = None
  243.     self.writer.new_margin(margin, len(fstack))
  244.  
  245.     def set_spacing(self, spacing):
  246.     self.spacing = spacing
  247.     self.writer.new_spacing(spacing)
  248.  
  249.     def push_style(self, *styles):
  250.     if self.softspace:
  251.         self.hard_break = self.para_end = self.softspace = 0
  252.         self.nospace = 1
  253.         self.writer.send_flowing_data(' ')
  254.     for style in styles:
  255.         self.style_stack.append(style)
  256.     self.writer.new_styles(tuple(self.style_stack))
  257.  
  258.     def pop_style(self, n=1):
  259.     del self.style_stack[-n:]
  260.     self.writer.new_styles(tuple(self.style_stack))
  261.  
  262.     def assert_line_data(self, flag=1):
  263.     self.nospace = self.hard_break = not flag
  264.     self.para_end = self.parskip = self.have_label = 0
  265.  
  266.  
  267. class NullWriter:
  268.     """Minimal writer interface to use in testing.
  269.     """
  270.     def __init__(self): pass
  271.     def new_alignment(self, align): pass
  272.     def new_font(self, font): pass
  273.     def new_margin(self, margin, level): pass
  274.     def new_spacing(self, spacing): pass
  275.     def new_styles(self, styles): pass
  276.     def send_paragraph(self, blankline): pass
  277.     def send_line_break(self): pass
  278.     def send_hor_rule(self, *args, **kw): pass
  279.     def send_label_data(self, data): pass
  280.     def send_flowing_data(self, data): pass
  281.     def send_literal_data(self, data): pass
  282.  
  283.  
  284. class AbstractWriter(NullWriter):
  285.  
  286.     def __init__(self):
  287.     pass
  288.  
  289.     def new_alignment(self, align):
  290.     print "new_alignment(%s)" % `align`
  291.  
  292.     def new_font(self, font):
  293.     print "new_font(%s)" % `font`
  294.  
  295.     def new_margin(self, margin, level):
  296.     print "new_margin(%s, %d)" % (`margin`, level)
  297.  
  298.     def new_spacing(self, spacing):
  299.     print "new_spacing(%s)" % `spacing`
  300.  
  301.     def new_styles(self, styles):
  302.     print "new_styles(%s)" % `styles`
  303.  
  304.     def send_paragraph(self, blankline):
  305.     print "send_paragraph(%s)" % `blankline`
  306.  
  307.     def send_line_break(self):
  308.     print "send_line_break()"
  309.  
  310.     def send_hor_rule(self, *args, **kw):
  311.     print "send_hor_rule()"
  312.  
  313.     def send_label_data(self, data):
  314.     print "send_label_data(%s)" % `data`
  315.  
  316.     def send_flowing_data(self, data):
  317.     print "send_flowing_data(%s)" % `data`
  318.  
  319.     def send_literal_data(self, data):
  320.     print "send_literal_data(%s)" % `data`
  321.  
  322.  
  323. class DumbWriter(NullWriter):
  324.  
  325.     def __init__(self, file=None, maxcol=72):
  326.     self.file = file or sys.stdout
  327.     self.maxcol = maxcol
  328.     NullWriter.__init__(self)
  329.     self.reset()
  330.  
  331.     def reset(self):
  332.     self.col = 0
  333.     self.atbreak = 0
  334.  
  335.     def send_paragraph(self, blankline):
  336.     self.file.write('\n' + '\n'*blankline)
  337.     self.col = 0
  338.     self.atbreak = 0
  339.  
  340.     def send_line_break(self):
  341.     self.file.write('\n')
  342.     self.col = 0
  343.     self.atbreak = 0
  344.  
  345.     def send_hor_rule(self, *args, **kw):
  346.     self.file.write('\n')
  347.     self.file.write('-'*self.maxcol)
  348.     self.file.write('\n')
  349.     self.col = 0
  350.     self.atbreak = 0
  351.  
  352.     def send_literal_data(self, data):
  353.     self.file.write(data)
  354.     i = string.rfind(data, '\n')
  355.     if i >= 0:
  356.         self.col = 0
  357.         data = data[i+1:]
  358.     data = string.expandtabs(data)
  359.     self.col = self.col + len(data)
  360.     self.atbreak = 0
  361.  
  362.     def send_flowing_data(self, data):
  363.     if not data: return
  364.     atbreak = self.atbreak or data[0] in string.whitespace
  365.     col = self.col
  366.     maxcol = self.maxcol
  367.     write = self.file.write
  368.     for word in string.split(data):
  369.         if atbreak:
  370.         if col + len(word) >= maxcol:
  371.             write('\n')
  372.             col = 0
  373.         else:
  374.             write(' ')
  375.             col = col + 1
  376.         write(word)
  377.         col = col + len(word)
  378.         atbreak = 1
  379.     self.col = col
  380.     self.atbreak = data[-1] in string.whitespace
  381.  
  382.  
  383. def test(file = None):
  384.     w = DumbWriter()
  385.     f = AbstractFormatter(w)
  386.     if file:
  387.     fp = open(file)
  388.     elif sys.argv[1:]:
  389.     fp = open(sys.argv[1])
  390.     else:
  391.     fp = sys.stdin
  392.     while 1:
  393.     line = fp.readline()
  394.     if not line:
  395.         break
  396.     if line == '\n':
  397.         f.end_paragraph(1)
  398.     else:
  399.         f.add_flowing_data(line)
  400.     f.end_paragraph(0)
  401.  
  402.  
  403. if __name__ == '__main__':
  404.     test()
  405.